﻿using System;
using System.Collections.Generic;
using System.IO.Ports;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Timers;
using System.Globalization;

namespace PitLib
{
    public class Car
    {
        #region Fields

        private const byte _endOfPackage = 255;

        private DateTime _dateTime;
        private double _amperage;
        private double _distance;
        private int _section;
        private double _speed;
        private double _voltage;
        private double _wattage;
        private double _temperature;

        private SerialPort _serialPort;
        private bool _initialised;

        private List<byte> _dataReceived;
        private bool _checksumIsNextToReceive;

        private bool _communicatingWithCar;
        private int _countCorruptPacketReceived;

        //This is for noticing CarController that there is new data to show
        public event Action<DateTime, double, double, int, double, double, double, double> EventNewData;

        public event Action<string> ExceptionOccured;

        #endregion

        /// <summary>
        /// Constructor
        /// </summary>
        public Car()
        {
            _dataReceived = new List<byte>();
        }

        /// <summary>
        /// Initialise serial connection
        /// </summary>
        /// <param name="XBeePort">portname of the Xbee port</param>
        /// <returns>bool opened</returns>
        public bool Init(string XBeePort)
        {
            _serialPort = new SerialPort(XBeePort, 115200, Parity.None, 8, StopBits.One);
            _serialPort.DataReceived += serialPort_DataReceived;
            bool isOpen = OpenPort();

            _checksumIsNextToReceive = false;
            _countCorruptPacketReceived = 0;
            return isOpen;
        }

        /// <summary>
        /// Send a message indicating the pit is alive
        /// </summary>
        public void SendAlive()
        {
            try
            {
                if (!_communicatingWithCar && _serialPort.IsOpen)
                {
                    _serialPort.WriteLine("pit");
                }
            }
            catch (Exception ex)
            {
                ExceptionOccured(ex.Message);
            }
        }

        /// <summary>
        /// Data received event handler
        /// </summary>
        private void serialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            _communicatingWithCar = true;
            if (_serialPort.IsOpen)
            {
                byte[] incoming = null;
                try
                {
                    int count = _serialPort.BytesToRead;
                    incoming = new byte[count];
                    _serialPort.Read(incoming, 0, count);
                }
                catch (Exception ex)
                {
                    ExceptionOccured(ex.Message);
                }

                for (int i = 0; i < incoming.Length; i++)
                {
                    if (incoming[i] == _endOfPackage && !_checksumIsNextToReceive)
                    {
                        _checksumIsNextToReceive = true;
                    }
                    else if (_checksumIsNextToReceive)
                    {
                        byte[] receivedData = _dataReceived.ToArray<byte>();

                        if (CRC.checksumCorrect(receivedData, incoming[i]))
                        {
                            string totalReceived = Encoding.ASCII.GetString(receivedData);
                            packageReceived(totalReceived);
                        }
                        else
                        {
                            string wrongReceived = Encoding.ASCII.GetString(receivedData);
                            _countCorruptPacketReceived++;
                        }
                        _checksumIsNextToReceive = false;
                        _dataReceived = new List<byte>();
                    }
                    else
                    {
                        _dataReceived.Add(incoming[i]);
                    }
                }
            }
            _communicatingWithCar = false;
        }

        /// <summary>
        /// Extract data from data string (package)
        /// </summary>
        /// <param name="data">data package string</param>
        private void packageReceived(string data)
        {
            string[] dataValues = data.Split(';');
            try
            {
                DateTime.TryParseExact(dataValues[0], "M-d-yyyy HH:mm:ss", null, DateTimeStyles.None, out _dateTime);

                _amperage = Convert.ToDouble(dataValues[1], CultureInfo.InvariantCulture);
                _distance = Convert.ToDouble(dataValues[2], CultureInfo.InvariantCulture);
                _section = Convert.ToInt32(dataValues[3], CultureInfo.InvariantCulture);
                _speed = Convert.ToDouble(dataValues[4], CultureInfo.InvariantCulture);
                _voltage = Convert.ToDouble(dataValues[5], CultureInfo.InvariantCulture);
                _wattage = Convert.ToDouble(dataValues[6], CultureInfo.InvariantCulture);
                _temperature = Convert.ToDouble(dataValues[7], CultureInfo.InvariantCulture);
            }
            catch (FormatException ex)
            {
                ExceptionOccured(ex.Message);
            }

            try
            {
                _serialPort.WriteLine("ack");
            }
            catch (SystemException ex)
            {
                ExceptionOccured(ex.Message);
            }

            //Send NewData event
            EventNewData(_dateTime, _amperage, _distance, _section, _speed, _voltage, _wattage, _temperature);
        }

        /// <summary>
        /// For opening the COM port to the arduino board
        /// </summary>
        /// <returns></returns>
        private bool OpenPort()
        {
            //Only open port when not open
            if (!_initialised && _serialPort.IsOpen == false)
            {
                try
                {
                    _serialPort.Open();
                    _initialised = true;
                }
                catch (Exception ex)
                {
                    ExceptionOccured(ex.Message);
                }
            }
            return _initialised;
        }

        /// <summary>
        /// For closing the COM port to the arduino board
        /// </summary>
        /// <returns>initialised bool</returns>
        public bool ClosePort()
        {
            //Only close port when open
            if (_initialised && _serialPort.IsOpen == true)
            {
                try
                {
                    _serialPort.Close();
                    _initialised = false;
                }
                catch (Exception ex)
                {
                    ExceptionOccured(ex.Message);
                }
            }
            return _initialised;
        }

        public bool GetCOMStatus()
        {
            try
            {
                return _serialPort.IsOpen;
            }
            catch (Exception ex)
            {
                ExceptionOccured(ex.Message);
                return false;
            }
        }
    }
}